package com.ssm.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.rememberme.InvalidCookieException;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;

/**
 * 让 remember me 功能更安全的方式就是将用户的 IP 地址绑定到cookie 的内容上
 * 我们需要临时存储 HttpServletRequest （将使用它来得到用户的 IP地址）
 * 到一个 ThreadLocal 中，因为基类中的一些方法并没有将 HttpServletRequest 作为一个参数
 */

/**
 * 设置和获取 ThreadLocal HttpServletRequest
 */
public class IPTokenBasedRememberMeServices  extends TokenBasedRememberMeServices {

    private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<HttpServletRequest>();

    public HttpServletRequest getContext() {
        return requestHolder.get();
    }

    public void setContext(HttpServletRequest context) {
        requestHolder.set(context);

    }

    /**
     * step1
     * 它用来为 remember me 处理设置cookie的值。在这个方法中，我们需要设置 ThreadLocal 并在完成处理后将其清除。
     * 需要记住的是父类方法的处理流程——收集用户的所有认证请求信息并将其合成到cookie 中
     * @param request
     * @param response
     * @param successfulAuthentication
     */
    @Override
    public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {

        try {
            setContext(request);
            super.onLoginSuccess(request, response, successfulAuthentication);

            SecurityContextHolder.getContext().setAuthentication(successfulAuthentication);
        } finally {
            setContext(null);
        }
    }

    /**
     * step2
     *创建认证凭证的 MD5 哈希值,能否用BCryt生成jwttoken
     *
     * @param tokenExpiryTime
     * @param username
     * @param password
     * @return
     */
    @Override
    protected String makeTokenSignature(long tokenExpiryTime, String username, String password) {
        return DigestUtils.md5DigestAsHex((username + ":" + tokenExpiryTime + ":" + password + ":" + getKey() + ":" + getUserIPAddress(getContext())).getBytes());
    }

    /**
     * step3
     * 得到了生成新cookie 所有需要的信息
     * @param tokens
     * @param maxAge
     * @param request
     * @param response
     */
    @Override
    protected void setCookie(String[] tokens, int maxAge, HttpServletRequest request, HttpServletResponse response) {
        // append the IP adddress to the cookie
        String[] tokensWithIPAddress =Arrays.copyOf(tokens, tokens.length + 1);
        tokensWithIPAddress [tokensWithIPAddress.length-1]  = getUserIPAddress(request);
        super.setCookie(tokensWithIPAddress, maxAge,request, response);

    }

    /**
     * new step1
     * 为了避免调用父类冗长的代码，我们在调用它之前先进行了一次 IP 地址的校验
     * @param cookieTokens
     * @param request
     * @param response
     * @return
     */
    @Override
    protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
        try{
            setContext(request);
            // take off the last token
            String ipAddressToken = cookieTokens[cookieTokens.length-1];
            if(!getUserIPAddress(request).equals(ipAddressToken)){
                throw new InvalidCookieException("Cookie IP Address did not contain a matching IP (contained '" + ipAddressToken + "')");
            }
            return super.processAutoLoginCookie(Arrays.copyOf(cookieTokens, cookieTokens.length-1), request, response);
        }
        finally{
                setContext(null);
        }
    }

    /**
     * 获取访问IP地址
     * 验证为什么不能放到一个类里面进行调用！！！！！！！！！！！！！！！
     * @param request
     * @return
     */
    public String getUserIPAddress(HttpServletRequest request) {
        return request.getRemoteAddr();
    }

}
